package cluster

import (
	"errors"
	"fmt"
	"gitee.com/xcy871622040/gameframe/cluster/runtime"
	"gitee.com/xcy871622040/gameframe/cluster/settings"
	"github.com/spf13/viper"
	"log"
	"math/rand"
	"reflect"
	"sync"
	"time"
)

var DeveloperDefinedRuntime = map[string]runtime.Runtime{}

type app struct {
	global     *sync.Map  // 存储全局变量
	Random     *rand.Rand // 随机数生成器
	nodeidonce *sync.Once // nodeid单例

	Plugins plugins
	Runtime runtime.Runtime

	Allocator Allocator

	Setting func() *settings.Setting
}

func (a *app) SetSetting(setting settings.Setting) {
	a.Setting = func() *settings.Setting { return &setting }
}

func (a *app) GlobalSet(name string, value interface{}) {
	a.global.Store(name, value)
}

func (a *app) GlobalAttr(name string, value interface{}) {
	if reflect.TypeOf(value).Kind() != reflect.Ptr {
		panic("app.GlobalGet 'value' Take an argument ptr type")
	}
	refv := reflect.ValueOf(value)

	if temp, ok := a.global.Load(name); ok {
		if reflect.TypeOf(temp).Kind() != reflect.Ptr {
			refv.Elem().Set(reflect.ValueOf(temp))
		} else {
			refv.Elem().Set(reflect.ValueOf(temp).Elem())
		}
	}
}

func (a *app) InstallRuntime(t, listener string) error {
	switch t {
	case "default":
		a.Runtime = runtime.NewDefaultRuntime()
	default:
		var ok bool
		if a.Runtime, ok = DeveloperDefinedRuntime[t]; ok {
			return errors.New("unknown type")
		}
	}

	if a.Setting() != nil {
		a.Runtime.Init(listener, a.Setting(), uint16(a.Random.Intn(8999)+1000))
	} else {
		return fmt.Errorf("app.Setting is nil")
	}
	return nil
}

// App 全局应用单例
var App = func() func() *app {
	x := new(app)
	x.global = new(sync.Map)
	x.nodeidonce = new(sync.Once)

	// 加载配置
	var setting *settings.Setting
	settingLoadOnce := sync.Once{}
	x.Setting = func() *settings.Setting {
		settingLoadOnce.Do(func() {
			setting = new(settings.Setting)
			if err := setting.Load(); err != nil {
				if _, ok := err.(viper.ConfigFileNotFoundError); ok {
					setting = nil
				} else {
					log.Fatal("load setting file viper error:", err)
				}
			}
		})
		return setting
	}

	x.Random = rand.New(rand.NewSource(time.Now().UnixNano()))

	x.Plugins = plugins{
		mutex:   sync.Mutex{},
		storage: make([]ServicePlugin, 0),
	}

	return func() *app { return x }
}()
